/** @file gui_profiler.cpp
    @brief A performance profiler stats page */

#include "gui/gui_profiler.h"

using namespace G3;

G3::GUI::PROFILER_MANAGER G3::GUI::MainProfilerMgr;

//==============================================================================
// Constructors
//==============================================================================
G3::GUI::PROFILER_ENTRY::PROFILER_ENTRY (const std::string &aName, const long &aTimeAccum, const int &aDepth):
	STATS_ENTRY (IndentStr (aName, aDepth), aDepth) {

	TimeAccum = 0;
	Depth = 0;

	CurrentSample = 0;

	MinPercentage = 0.0f;
	MaxPercentage = 0.0f;
	AvgPercentage = 0.0f;

	if (aTimeAccum != 0)
		AddSample (aTimeAccum);
}

G3::GUI::PROFILER_MANAGER::PROFILER_MANAGER () {
	Depth = 0;
	NumMaxSamples = 30;
	TotalTime = 0;
	StatsPage = NULL;
	profilingEnabled = true;

	log = &G3::MainLog;
}

G3::GUI::PROFILER::PROFILER (const std::string &aName) {
    if (!MainProfilerMgr.profilingEnabled)
        return;

	MainProfilerMgr.AddDepth ();
	Name = aName;
	Timer.Start ();
}

//==============================================================================
// Deconstructors
//==============================================================================
G3::GUI::PROFILER_ENTRY::~PROFILER_ENTRY () {
	Clear ();
}

G3::GUI::PROFILER_MANAGER::~PROFILER_MANAGER () {
	std::map<std::string, PROFILER_ENTRY *>::iterator it = Entries.begin ();

    try {
        while (it != Entries.end ()) {
            delete it->second;

            it++;
        }

        Entries.clear ();

        delete StatsPage;
        StatsPage = NULL;
    } catch (...) {
        if (log)
            log->Report ("PROFILER_MANAGER::~PROFILER_MANAGER: Failed to free the class..\n");
    }
}

G3::GUI::PROFILER::~PROFILER () {
    if (!MainProfilerMgr.profilingEnabled)
        return;

	// The scope ended? Add our current sample to the profiler manager singleton
	MainProfilerMgr.AddSample (Name, Timer.CheckTime ());
	Name.clear ();
}

//==============================================================================
// Attaches a font to the entry
//==============================================================================
void G3::GUI::PROFILER_ENTRY::AttachFont (FONT *aFont) {
	Assert (aFont, "PROFILER_ENTRY::AttachFont: Font argument is a NULL pointer");

	Line.AttachFont (aFont);
}

//==============================================================================
// Adds a sample
//==============================================================================
void G3::GUI::PROFILER_ENTRY::AddSample (const long &aTimeSpent) {
	TimeAccum += aTimeSpent;

    // Pop the oldest if there are too many samples already
	if ((int) TimeSpent.size () >= MainProfilerMgr.NumMaxSamples)
        TimeSpent.pop_front ();

	TimeSpent.push_back (aTimeSpent);
}

//==============================================================================
// Calculates Min, Avg, Max percentages
//==============================================================================
void G3::GUI::PROFILER_ENTRY::CalcEntryPercentage (const long &aTotalTime) {
	std::list<float>::iterator it;

	double d1 = TimeAccum;
	double d2 = aTotalTime;
	float p = ((float) (d1 / d2)) * 100.0f;

    // Pop the oldest if there are too many samples already
	if ((int) Percentages.size () >= MainProfilerMgr.NumMaxSamples)
        Percentages.pop_front ();

	Percentages.push_back (p);

	TimeAccum = 0;

	MinPercentage = *std::min_element (Percentages.begin (), Percentages.end ());
	MaxPercentage = *std::max_element (Percentages.begin (), Percentages.end ());

	p = 0;
	it = Percentages.begin ();
	while (it != Percentages.end ()) {
		p += (*it);

		it++;
	}
	p /= ((float) Percentages.size ());

	AvgPercentage = p;
}

//==============================================================================
// Calculates Min, Avg, Max time spent on the scope
//==============================================================================
void G3::GUI::PROFILER_ENTRY::CalcEntryTimings () {
    if (TimeSpent.empty ())
		return;

	std::list<long>::iterator it = TimeSpent.begin ();

	MinimumTimeSpent = *std::min_element (TimeSpent.begin (), TimeSpent.end ());
	MaximumTimeSpent = *std::max_element (TimeSpent.begin (), TimeSpent.end ());

	long avg = 0;
	while (it != TimeSpent.end ()) {
		avg += (*it);

		it++;
	}
	avg /= ((long) TimeSpent.size ());

	AverageTimeSpent = avg;
}

//==============================================================================
// Calculate the total time spent on the scope
//==============================================================================
long G3::GUI::PROFILER_ENTRY::TotalTimeSpent () {
    return TimeAccum;
}

//==============================================================================
// Gets the minimum time spent on the scope
//==============================================================================
long G3::GUI::PROFILER_ENTRY::MinTimeSpent () {
    return MinimumTimeSpent;
}

//==============================================================================
// Gets the maximum time spent on the scope
//==============================================================================
long G3::GUI::PROFILER_ENTRY::MaxTimeSpent () {
	return MaximumTimeSpent;
}

//==============================================================================
// Clears the profiler entry
//==============================================================================
void G3::GUI::PROFILER_ENTRY::Clear () {
	TimeSpent.clear ();
	Percentages.clear ();
}

//==============================================================================
// Draws an entry
//==============================================================================
void G3::GUI::PROFILER_ENTRY::Draw (const VEC2I &Position, bool Highlighted, float aElapsedTime) {
	Line.SetPosition (Position);

	Line.Print ("%s: Min %.2f%% Avg %.2f%% Max %.2f%%", Label.c_str (), MinPercentage, AvgPercentage, MaxPercentage);

	if (Highlighted)
		Line.Color = COL4F (1.0f, 1.0f, 1.0f, 1.0f);
	else
		Line.Color = COL4F (0.7f, 0.7f, 0.7f, 1.0f);

	Line.Draw (aElapsedTime);
}

//==============================================================================
// Marks the start of a profiled scope
//==============================================================================
void G3::GUI::PROFILER_MANAGER::AddDepth () {
	std::map<std::string, PROFILER_ENTRY *>::iterator it = Entries.begin ();

	if (Depth == 0) {
		while (it != Entries.end ()) {
			it->second->CalcEntryPercentage (TotalTime);
			it->second->CalcEntryTimings ();

			it++;
		}
		TotalTime = 0;
	}
	++Depth;
}

//==============================================================================
// Adds the profiler information to a Stats page
//==============================================================================
void G3::GUI::PROFILER_MANAGER::AddStatsPage (FONT *aFont, const RECTI &aRect) {
    try {
        StatsPage = new STATS_PAGE (aFont, "Performance profile", aRect);

        if (!StatsPage)
            throw 1;
    } catch (...) {
        if (log)
            log->Report ("PROFILER_MANAGER::AddStatsPage: Not enough memory for a performance profile..\n");
        return;
    }

	MainStats.AddPage (StatsPage);
	// Attach the font to the profiler entries
	std::map<std::string, PROFILER_ENTRY *>::iterator it = Entries.begin ();
	while (it != Entries.end ()) {
		it->second->AttachFont (aFont);

		it++;
	}
}

//==============================================================================
// Adds a sample to the profiler manager
//==============================================================================
void G3::GUI::PROFILER_MANAGER::AddSample (const std::string &aName, const long &aTicks) {
	std::pair<std::map<std::string, PROFILER_ENTRY *>::iterator, bool> nipair;
	PROFILER_ENTRY *entry = NULL;

	try {
        entry = new PROFILER_ENTRY (aName, aTicks, Depth);

        if (!entry)
            throw 1;
	} catch (...) {
	    if (log)
            log->Report ("PROFILER_MANAGER::AddSample: Not enough memory for another sample..\n");
        return;
    }

	TotalTime += aTicks;

	nipair = Entries.insert (pair<std::string, PROFILER_ENTRY *> (aName, entry));
	if (!nipair.second) {
		nipair.first->second->AddSample (aTicks);

        try {
            delete entry;
        } catch (...) {
            if (log)
                log->Report ("PROFILER_MANAGER::AddSample: Failed to free a profiler entry..\n");
        }
	} else {
		if (StatsPage) {
			StatsPage->AddEntry (nipair.first->second);
			// Pass all its entries the same font
			StatsPage->AttachFont (StatsPage->Font);
		}
	}

	--Depth;
}

